#include "widget_edit_node.h"
#include "ui_widget_edit_node.h"
#include "input/input_file.h"
#include "input/input_color.h"
#include "input/input_text.h"
#include "input/input_check.h"
#include "input/input_combo.h"
#include <QCheckBox>
#include <QPushButton>
#include <QMessageBox>
#include <QStack>

WidgetEditNode::WidgetEditNode(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::WidgetEditNode)
{
    ui->setupUi(this);
    ui->formLayout->setRowWrapPolicy(QFormLayout::WrapLongRows);
}

WidgetEditNode::~WidgetEditNode()
{
    delete ui;
}

/**
 * @brief 新增指定类型节点
 * @param type
 * @param parent
 */
void WidgetEditNode::newNode(const QString &type, EntitySceneNode* parent)
{
    reset(nullptr, type);
    setupInput(type);
    if (parent) ui->comboBoxParent->setCurrentText(parent->Name);
    QPushButton* pushButton = new QPushButton(this);
    pushButton->setText("确认添加");
    ui->formLayout->addRow("", pushButton);
    connect(pushButton, &QPushButton::clicked, this, [=]() {
        QString pname = ui->comboBoxParent->currentText();
        QString name = ui->lineEditName->text();
        if (Program::SceneNodeList.count(name) > 0)
        {
            QMessageBox::warning(this, "错误", QString("已存在名称为 '%1' 的节点").arg(name));
            return;
        }
        auto piter = Program::SceneNodeList.find(pname);
        EntitySceneNode* pnode = piter != Program::SceneNodeList.end() ? piter.value().get() : nullptr;
        std::shared_ptr<EntitySceneNode> node = std::make_shared<EntitySceneNode>(pnode, type, name, "");
        if (updateAttribute(node.get()))
        {
            Program::SceneNodeList.insert(node->Name, node);
            emit sig_editApply(node.get());
            reset(nullptr, type, false);
        }
    });
}

/**
 * @brief 编辑指定名称节点信息
 * @param name
 */
void WidgetEditNode::editNode(const QString &name)
{
    std::shared_ptr<EntitySceneNode> node = Program::SceneNodeList[name];
    reset(node.get(), node->Type);
    fromAttribute(node.get());
    setupInput(node->Type);
    if (node->Parent) ui->comboBoxParent->setCurrentText(node->Parent->Name);
    QPushButton* pushButton = new QPushButton(this);
    pushButton->setText("确认修改");
    ui->formLayout->addRow("", pushButton);
    connect(pushButton, &QPushButton::clicked, this, [=]() {
        QString pname = ui->comboBoxParent->currentText();
        auto piter = Program::SceneNodeList.find(pname);
        if (updateAttribute(node.get()))
        {
            node->Parent = piter != Program::SceneNodeList.end() ? piter.value().get() : nullptr;
            emit sig_editApply(node.get());
        }
    });
}

/**
 * @brief 重置属性列表
 * @param node
 * @param type
 * @param clearWidgets
 */
void WidgetEditNode::reset(const EntitySceneNode* node, const QString& type, bool clearWidgets)
{
    QStringList nodeList;
    QMap<QString, int> nodeTypeCount;
    QMapIterator<QString, std::shared_ptr<EntitySceneNode>> iter(Program::SceneNodeList);
    while (iter.hasNext())
    {
        iter.next();
        bool isChildren = false;
        EntitySceneNode* parent = iter.value()->Parent;
        while (parent)
        {
            isChildren = parent == node;
            if (isChildren) break;
            parent = parent->Parent;
        }
        if (!isChildren) nodeList.append(iter.value()->Name);
        nodeTypeCount[iter.value()->Type] = nodeTypeCount[iter.value()->Type] + 1;
    }
    if (node) nodeList.removeOne(node->Name);
    ui->comboBoxParent->clear();
    ui->comboBoxParent->addItems(nodeList);
    ui->comboBoxParent->setCurrentIndex(-1);
    if (node)
    {
        ui->lineEditType->setText(node->Type);
        ui->lineEditName->setText(node->Name);
        ui->lineEditName->setReadOnly(true);
    }
    else
    {
        ui->lineEditType->setText(type);
        ui->lineEditName->setText(type + "_" + QString::number(nodeTypeCount[type] + 1));
        ui->lineEditName->setReadOnly(false);
    }
    if (clearWidgets)
    {
        _attributeMapInit.clear();
        while (ui->formLayout->rowCount() > 3) ui->formLayout->removeRow(3);
    }
}

/**
 * @brief 根据类型创建属性列表
 * @param type
 * @param int
 */
void WidgetEditNode::setupInput(const QString &type)
{
    if (type == "animation")
    {
        addRowFile(1, "模型文件", "meshFile", "Skeleton Model (*.dae *.ms3d *.bvh *.glb *.gltf *.vrm)");
        addRowCheck(2, "开启动画", "start", "true");
        addRowCheck(3, "显示骨骼", "skeleton", "false");
    }
    else if (type == "static")
    {
        addRowFile(1, "模型文件", "meshFile", "3D Model (*.dae *.ms3d *.bvh *.glb *.gltf *.vrm *.stl *.obj *.3ds *.b3dm *.i3dm)");
    }
    else if (type == "geometry")
    {
        addRowFile(1, "材质纹理", "texture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)", true);
        addRowCheck(2, "法向反转", "reversed", "false");
        addRowCombo(3, "几何类型", "shape", QStringList({"box:立方体", "sphere:球体", "capsule:胶囊体", "cylinder:圆柱体", "torus:圆环", "cone:圆锥体", "plane:平面", "circle:圆形", "spiral:螺旋体"}), [=](int index){
            switch (index)
            {
            case 0:
                addRowText(4, "长度", "x", "float", "1");
                addRowText(5, "宽度", "y", "float", "1");
                addRowText(6, "高度", "z", "float", "1");
                break;
            case 1:
            case 7:
                addRowText(4, "半径", "r", "float", "1");
                addRowText(5, "细分数", "slices", "int", "32");
                break;
            case 2:
            case 3:
            case 5:
                addRowText(4, "半径", "r", "float", "1");
                addRowText(5, "高度", "h", "float", "1");
                addRowText(6, "细分数", "slices", "int", "32");
                break;
            case 4:
                addRowText(4, "圆环半径", "r", "float", "1");
                addRowText(5, "截面半径", "t", "float", "1");
                addRowText(6, "细分数", "slices", "int", "32");
                break;
            case 6:
                addRowText(4, "宽度", "w", "float", "1");
                addRowText(5, "高度", "h", "float", "1");
                break;
            case 8:
                addRowText(4, "弧度", "a", "float", "6.28");
                addRowText(5, "半径", "r", "float", "1");
                addRowText(6, "层级间隔", "p", "float", "1");
                addRowText(7, "截面半径", "t", "float", "1");
                addRowText(8, "细分数", "slices", "int", "32");
                break;
            }
        });
    }
    else if (type == "skybox")
    {
        addRowFile(1, "右侧纹理", "texture0", "Image (*.jpg *.jpeg *.png)");
        addRowFile(2, "左侧纹理", "texture1", "Image (*.jpg *.jpeg *.png)");
        addRowFile(3, "前侧纹理", "texture2", "Image (*.jpg *.jpeg *.png)");
        addRowFile(4, "后侧纹理", "texture3", "Image (*.jpg *.jpeg *.png)");
        addRowFile(5, "上侧纹理", "texture4", "Image (*.jpg *.jpeg *.png)");
        addRowFile(6, "下侧纹理", "texture5", "Image (*.jpg *.jpeg *.png)");
    }
    else if (type == "water")
    {
        addRowFile(1, "法线纹理", "normalMap", "Image (*.jpg *.jpeg *.png *.tga)");
        addRowColor(2, "水面颜色", "color", "#024D3AE6");
        addRowCombo(3, "水面形状", "shape", QStringList({"plane:平面", "circle:圆形"}), nullptr, "string", "plane");
        addRowText(4, "区域大小", "size", "float", "1000");
    }
    else if (type == "board")
    {
        addRowFile(1, "纹理贴图", "texture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(2, "宽度", "width", "float", "1");
        addRowText(3, "高度", "height", "float", "1");
        addRowCombo(4, "跟随类型", "billboard", {"0:静止", "1:定轴", "2:定视角", "3:无限制"}, nullptr, "int", "0");
    }
    else if (type == "cloud")
    {
        addRowColor(1, "天空颜色", "skyColor", "#2265B3FF");
        addRowColor(2, "云层颜色", "cloudColor", "#CCCCCCFF");
        addRowText(3, "云朵大小", "cloudSize", "float", "20");
    }
    else if (type == "screen")
    {
        addRowFile(1, "纹理贴图", "texture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(2, "像素宽度", "width", "int", "256");
        addRowText(3, "像素高度", "height", "int", "256");
    }
    else if (type == "flame")
    {
        addRowFile(1, "纹理贴图", "texture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(2, "扰动图", "distortionMap", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(3, "透明蒙版", "maskMap", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(4, "宽度", "width", "float", "1");
        addRowText(5, "高度", "height", "float", "1");
    }
    else if (type == "terrain")
    {
        addRowFile(1, "高程图", "heightMap", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(2, "地形缩放", "mapScale", "float", "1000");
        addRowText(3, "最下高程", "minHeight", "float", "0");
        addRowText(4, "最大高程", "maxHeight", "float", "100");
        addRowText(5, "几何精度", "errorTolerance", "float", "10");
        addRowFile(6, "纹理1", "texture0", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(7, "纹理2", "texture1", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(8, "纹理3", "texture2", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(9, "纹理4", "texture3", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(10, "混合蒙版", "blendMask", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(11, "混合纹理", "blendTexture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
    }
    else if (type == "text")
    {
        addRowText(1, "文本", "text", "string", "文本");
        addRowColor(2, "字体颜色", "color", "#FF0000FF");
        addRowColor(3, "轮廓颜色", "outline", "#FFFFFF00");
        addRowText(4, "字体名称", "font", "string", "default");
        addRowText(5, "字体大小", "fontSize", "int", "16");
    }
    else if (type == "mirror")
    {
        addRowText(1, "宽度", "width", "float", "1");
        addRowText(2, "高度", "height", "float", "1");
        addRowFile(3, "蒙版", "maskMap", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
    }
    else if (type == "particles")
    {
        addRowFile(1, "纹理贴图", "texture", "Image (*.png *.tga)");
        addRowText(2, "粒子大小", "size", "float", "0.5");
        addRowCheck(3, "变暗混合", "darken");
        addRowText(4, "粒子数量", "count", "int", "100");
        addRowCheck(5, "循环发射", "loop", "true");
    }
    else if (type == "planet")
    {
        addRowFile(1, "白昼图片", "texture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(2, "黑夜图片", "textureNight", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(3, "半径", "radius", "float", "10");
        addRowText(4, "细分数", "slices", "int", "64");
    }
    else if (type == "plant")
    {
        addRowFile(1, "纹理贴图", "texture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(2, "植被宽度", "width", "float", "1");
        addRowText(3, "植被高度", "height", "float", "1");
        addRowFile(4, "分布图", "distributionMap", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(5, "总数", "count", "int", "100");
        addRowText(6, "分布密度", "density", "float", "0.3");
        addRowText(7, "区域大小", "regionSize", "float", "1000");
        addRowText(8, "缩放范围", "scaleRange", "float", "0.5");
    }
    else if (type == "projector")
    {
        addRowFile(1, "投影纹理", "texture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(2, "投影视角", "fov", "float", "60");
        addRowText(3, "宽高比", "aspect", "float", "1");
        addRowText(4, "近平面", "zNear", "float", "0.5");
        addRowText(5, "远平面", "zFar", "float", "1000");
        addRowCheck(6, "显示线框", "projectionLine");
        addRowCheck(7, "遮挡剔除", "occlusionCulling");
        addRowText(8, "投影容差", "errorTolerance", "float", "0.001");
    }
    else if (type == "sheet")
    {
        addRowFile(1, "表单纹理", "texture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(2, "行数", "row", "int", "1");
        addRowText(3, "列数", "column", "int", "1");
    }
    else if (type == "sound")
    {
        addRowFile(1, "音频文件", "soundFile", "Sound (*.mp3 *.wav)");
        addRowCheck(2, "循环播放", "loop");
        addRowCheck(3, "自动播放", "start");
    }
    // extra scenenode
    else if (type == "2dtiles")
    {
        addRowFile(1, "数据源", "source", "All (*.*)");
        addRowText(2, "范围大小", "size", "float", "1000");
        addRowText(3, "最高层级", "maxLevel", "int", "8");
        addRowText(4, "缓存大小", "cacheSize", "int", "500");
    }
    else if (type == "3dtiles")
    {
        addRowFile(1, "数据源", "jsonFile", "3dtiles (tileset.json)");
        addRowText(2, "缓存大小", "cacheSize", "int", "500");
    }
    else if (type == "placemark")
    {
        addRowFile(1, "图标", "image", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(2, "文字标注", "text", "string", "?");
        addRowColor(3, "文字颜色", "color", "#FF0000FF");
        addRowColor(4, "轮廓颜色", "outline", "#FFFF00FF");
        addRowText(5, "字体名称", "font", "string", "default");
        addRowText(6, "字体大小", "fontSize", "int", "16");
    }
    else if (type == "plot")
    {
        addRowText(1, "屏幕宽度", "width", "float", "256");
        addRowText(2, "屏幕高度", "height", "float", "256");
    }
    else if (type == "radiation")
    {
        addRowFile(1, "纹理颜色", "texture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(2, "辐射数量", "count", "int", "5");
        addRowText(3, "扩散速度", "speed", "float", "5");
        addRowText(4, "扩散半径", "radius", "float", "10");
    }
    else if (type == "rain")
    {
        addRowText(1, "雨水半径", "radius", "float", "100");
        addRowText(2, "雨水高点", "height", "float", "500");
        addRowText(3, "雨水密度", "density", "float", "1");
    }
    else if (type == "reference")
    {
        addRowText(1, "引用节点", "sceneNode", "string", "");
    }
    else if (type == "route")
    {
        addRowFile(1, "纹理图片", "texture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(2, "宽度", "width", "float", "1");
    }
    else if (type == "splatting")
    {
        addRowFile(1, "点云文件", "splatFile", "Splat file (*.splat)");
    }
    else if (type == "tree")
    {
        addRowFile(1, "树干纹理", "trunkTexture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(2, "树叶纹理", "leafTexture", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowText(3, "树干细分数", "slices", "int", "8");
        addRowText(4, "分支数量", "branch", "int", "4");
    }
    else if (type == "waterflow")
    {
        addRowFile(1, "法线纹理", "normalMap", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(2, "流动纹理", "flowMap", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
        addRowFile(3, "颜色纹理", "colorMap", "Image (*.jpg *.jpeg *.png *.bmp *.tga)");
    }
    else if (type == "wireframe")
    {
        addRowColor(1, "颜色", "color", "#FF0000FF");
        addRowText(2, "线框宽度", "width", "float", "1");
    }
}

/**
 * @brief 更新节点属性
 * @param node
 * @return
 */
bool WidgetEditNode::updateAttribute(EntitySceneNode* node)
{
    QStringList attribute;
    for (int i = 3; i < ui->formLayout->rowCount() - 1; i++)
    {
        QLabel* label = qobject_cast<QLabel*>(ui->formLayout->itemAt(i, QFormLayout::LabelRole)->widget());
        WidgetInput* widget = dynamic_cast<WidgetInput*>(ui->formLayout->itemAt(i, QFormLayout::FieldRole)->widget());
        if (widget->isEmpty())
        {
            QMessageBox::warning(this, "错误", QString("请输入%1").arg(label->text()));
            return false;
        }
        else attribute.append(widget->name() + "=" + widget->getValue());
    }
    toAttribute(node, attribute);
    return true;
}

/**
 * @brief 将节点属性字符串转换为属性表
 * @param node
 */
void WidgetEditNode::fromAttribute(EntitySceneNode* node)
{
    if (node->Attribute.isEmpty()) return;
    QList<QPair<QString, QString>> attributes;
    splitAttribute(node->Attribute, attributes);
    for (qsizetype i = 0; i < attributes.size(); i++) _attributeMapInit[attributes[i].first] = attributes[i].second;
    if (node->Type == "skybox" || node->Type == "terrain")
    {
        QStringList texture = _attributeMapInit["texture"].split(',');
        _attributeMapInit.remove("texture");
        for (int i = 0; i < texture.count(); i++)
        {
            _attributeMapInit[QString("texture%1").arg(i)] = texture[i];
        }
    }
}

/**
 * @brief 将 attribList 转换为节点属性字符串
 * @param node
 * @param attribList
 */
void WidgetEditNode::toAttribute(EntitySceneNode* node, QStringList& attribList)
{
    if (node->Type == "skybox" || node->Type == "terrain")
    {
        QStringList texture;
        for (qsizetype i = 0; i < attribList.size();)
        {
            if (attribList[i].startsWith("texture"))
            {
                qsizetype a = attribList[i].indexOf('\'') + 1;
                qsizetype b = attribList[i].lastIndexOf('\'');
                texture.append(attribList[i].mid(a, b - a));
                attribList.removeAt(i);
            } else i++;
        }
        attribList.append("texture='" + texture.join(',') + "'");
    }
    node->Attribute = attribList.join(',');
}

/**
 * @brief 拆分字符串
 * @param str
 * @param result
 * 例如 minHeight=0,maxHeight=100,mapScale=1000,heightMap='heightMap.png',texture='sand.jpg,grass.png,grass.jpg,rock.jpg'
 * 会被拆分为
 *  minHeight : 0
 *  maxHeight : 100
 *  mapScale : 1000
 *  heightMap : heightMap.png
 *  texture : sand.jpg,grass.png,grass.jpg,rock.jpg
 */
void WidgetEditNode::splitAttribute(const QString& str, QList<QPair<QString, QString>>& result)
{
    const QChar ch_comma = QChar::fromLatin1(',');
    const QChar ch_quot = QChar::fromLatin1('\'');
    const QChar ch_backslash = QChar::fromLatin1('\\');
    QStack<QChar> stack;
    stack.push(QChar::fromLatin1(' '));
    qsizetype name_start = 0;
    qsizetype index = str.indexOf('=', 0);
    qsizetype attr_start = index + 1;
    QString name = str.mid(name_start, index - name_start).trimmed();
    while (index >= 0 && index < str.length())
    {
        index += 1;
        QChar ch = index == str.length() ? ch_comma : str.at(index);
        if (ch == ch_backslash)
        {
            if (stack.top() == ch_backslash) stack.pop();
            else stack.push(ch);
        }
        else if (ch == ch_quot)
        {
            if (stack.top() == ch_backslash) stack.pop();
            else if (stack.top() == ch_quot) stack.pop();
            else stack.push(ch_quot);
        }
        else if (ch == ch_comma)
        {
            if (stack.size() == 1)
            {
                QString value = str.mid(attr_start, index - attr_start).trimmed();
                while (value.startsWith('\'')) value.remove(0, 1);
                while (value.endsWith('\'')) value.remove(value.length() - 1, 1);
                result.append(QPair<QString, QString>(name, value));
                name_start = index + 1;
                index = str.indexOf('=', name_start);
                attr_start = index + 1;
                if (index >= 0) name = str.mid(name_start, index - name_start).trimmed();
            }
        }
        else if (stack.top() == ch_backslash) stack.pop();
    }
}

void WidgetEditNode::addRowCheck(int row, const QString& title, const QString& name, const QString& defValue)
{
    auto iter = _attributeMapInit.find(name);
    InputCheck* widget = new InputCheck(this, name);
    widget->setValue(iter != _attributeMapInit.end() ? iter.value() : defValue);
    int index = row + 2;
    while (index < ui->formLayout->rowCount())
    {
        if (dynamic_cast<WidgetInput*>(ui->formLayout->itemAt(index, QFormLayout::FieldRole)->widget())) ui->formLayout->removeRow(index);
        else index++;
    }
    ui->formLayout->insertRow(row + 2, title, widget);
}

void WidgetEditNode::addRowColor(int row, const QString& title, const QString& name, const QString& defValue)
{
    auto iter = _attributeMapInit.find(name);
    InputColor* widget = new InputColor(this, name);
    widget->setValue(iter != _attributeMapInit.end() ? iter.value() : defValue);
    int index = row + 2;
    while (index < ui->formLayout->rowCount())
    {
        if (dynamic_cast<WidgetInput*>(ui->formLayout->itemAt(index, QFormLayout::FieldRole)->widget())) ui->formLayout->removeRow(index);
        else index++;
    }
    ui->formLayout->insertRow(row + 2, title, widget);
}

void WidgetEditNode::addRowCombo(int row, const QString& title, const QString& name, const QStringList& items, std::function<void(int)> onChange, const QString& type, const QString& defValue)
{
    auto iter = _attributeMapInit.find(name);
    InputCombo* widget = new InputCombo(this, name);
    widget->setItems(items);
    widget->setType(type);
    widget->setCallback(onChange);
    widget->setValue(iter != _attributeMapInit.end() ? iter.value() : defValue);
    int index = row + 2;
    while (index < ui->formLayout->rowCount())
    {
        if (dynamic_cast<WidgetInput*>(ui->formLayout->itemAt(index, QFormLayout::FieldRole)->widget())) ui->formLayout->removeRow(index);
        else index++;
    }
    ui->formLayout->insertRow(row + 2, title, widget);
}

void WidgetEditNode::addRowFile(int row, const QString& title, const QString& name, const QString& filter, bool allowEmpty, const QString& defValue)
{
    auto iter = _attributeMapInit.find(name);
    InputFile* widget = new InputFile(this, name);
    widget->setFilter(filter);
    widget->setAllowEmpty(allowEmpty);
    widget->setValue(iter != _attributeMapInit.end() ? iter.value() : defValue);
    int index = row + 2;
    while (index < ui->formLayout->rowCount())
    {
        if (dynamic_cast<WidgetInput*>(ui->formLayout->itemAt(index, QFormLayout::FieldRole)->widget())) ui->formLayout->removeRow(index);
        else index++;
    }
    ui->formLayout->insertRow(row + 2, title, widget);
}

void WidgetEditNode::addRowText(int row, const QString& title, const QString& name, const QString& type, const QString& defValue)
{
    auto iter = _attributeMapInit.find(name);
    InputText* widget = new InputText(this, name);
    widget->setType(type);
    widget->setValue(iter != _attributeMapInit.end() ? iter.value() : defValue);
    int index = row + 2;
    while (index < ui->formLayout->rowCount())
    {
        if (dynamic_cast<WidgetInput*>(ui->formLayout->itemAt(index, QFormLayout::FieldRole)->widget())) ui->formLayout->removeRow(index);
        else index++;
    }
    ui->formLayout->insertRow(row + 2, title, widget);
}
